# gozerbot/callbacks.py
#
#

""" 
    bot callbacks .. callbacks occure on registered events. a precondition 
    function can optionaly be provided to see if the callback should fire. 
    callback can be executed in a Runner (1 thread that executes more jobs)
    or in a seperate thread. callbacks are now unified for bot irc and jabber 
    events.
"""

__status__ = "seen"

## gozerbot imports

from gozerbot.stats import stats
from gozerbot.threads.thr import getname 
from config import config
from utils.log import rlog
from utils.exception import handle_exception
from utils.trace import calledfrom
from utils.generic import makeargrest
from utils.locking import lockdec
from utils.dol import Dol
from threads.thr import start_new_thread, getname
from runner import cbrunners

## basic imports

import sys
import copy
import thread

## locks

callbacklock = thread.allocate_lock()
locked = lockdec(callbacklock)

## Callback class

class Callback(object):

    """ class representing a callback. """

    def __init__(self, func, prereq, plugname, kwargs, threaded=False, speed=5):
        self.func = func # the callback function
        self.prereq = prereq # pre condition function
        self.plugname = plugname # plugin name
        self.kwargs = kwargs # kwargs to pass on to function
        self.threaded = copy.deepcopy(threaded) # run callback in thread
        self.speed = copy.deepcopy(speed) # speed to execute callback with
        self.activate = False
        stats.up('callbacks', 'created')

## Callbacks class

class Callbacks(object):

    """ 
        dict of lists containing callbacks.  Callbacks object take care of 
        dispatching the callbacks based on incoming events. see Callbacks.check()

    """

    def __init__(self):
        self.cbs = Dol()

    def size(self):
        """ return number of callbacks. """
        return len(self.cbs)

    def add(self, what, func, prereq=None, kwargs=None, threaded=False, nr=False, speed=5):
        """  add a callback. """
        what = what.upper()
        plugname = calledfrom(sys._getframe(0))
        if config['loadlist'] and not plugname in config['loadlist']:
            rlog(-1, plugname, 'not in loadlist .. not adding callback')
            return
        if not kwargs: kwargs = {}
        if nr != False: self.cbs.insert(nr, what, Callback(func, prereq, plugname, kwargs, threaded, speed))
        else: self.cbs.add(what, Callback(func, prereq, plugname, kwargs, threaded, speed))
        rlog(0, 'callbacks', 'added %s (%s)' % (what, plugname))

    def unload(self, plugname):
        """ unload all callbacks registered in a plugin. """
        unload = []
        for name, cblist in self.cbs.iteritems():
            index = 0
            for item in cblist:
                if item.plugname == plugname: unload.append((name, index))
                index += 1
        for callback in unload[::-1]:
            self.cbs.delete(callback[0], callback[1])
            rlog(1, 'callbacks', 'unloaded %s' % callback[0])

    def disable(self, plugname):
        """ disable all callbacks registered in a plugin. """
        unload = []
        for name, cblist in self.cbs.iteritems():
            index = 0
            for item in cblist:
                if item.plugname == plugname: item.activate = False

    def activate(self, plugname):
        """ activate all callbacks registered in a plugin. """
        unload = []
        for name, cblist in self.cbs.iteritems():
            index = 0
            for item in cblist:
                if item.plugname == plugname: item.activate = True

    def whereis(self, cmnd):
        """ show where ircevent.CMND callbacks are registered """
        result = []
        cmnd = cmnd.upper()
        for c, callback in self.cbs.iteritems():
            if c == cmnd:
                for item in callback:
                    if not item.plugname in result: result.append(item.plugname)
        return result

    def list(self):
        """ show all callbacks. """
        result = []
        for cmnd, callbacks in self.cbs.iteritems():
            for cb in callbacks: result.append(getname(cb.func))
        return result

    def check(self, bot, ievent):
        """  check for callbacks to be fired. """
        if self.cbs.has_key('ALL'):
            for cb in self.cbs['ALL']:
                stats.up('callbacks', 'ALL')
                self.callback(cb, bot, ievent)
        cmnd = ievent.cbtype or ievent.cmnd.upper()
        if self.cbs.has_key(cmnd):
            for cb in self.cbs[cmnd]:
                stats.up('callbacks', cmnd)
                self.callback(cb, bot, ievent)

    def callback(self, cb, bot, ievent):
        """ do the actual callback with provided bot and ievent as arguments. """
        try:
            if not cb.activate: return
            if cb.prereq:
                rlog(0, 'callbacks', 'excecuting in loop %s' % str(cb.prereq))
                if not cb.prereq(bot, ievent): return
            if not cb.func: return
            rlog(0, 'callbacks', 'excecuting callback %s' % str(cb.func))
            stats.up('callbacks', getname(cb.func))
            stats.up('callbacks', cb.plugname)
            ievent.iscallback = True
            if cb.threaded: start_new_thread(cb.func, (bot, ievent), cb.kwargs)
            else: cbrunners.put(cb.plugname, cb.func, bot, ievent, **cb.kwargs)
        except Exception, ex: handle_exception()

## defines

callbacks = jcallbacks = Callbacks()
gn_callbacks = Callbacks()
